package com.tanfuqi.sicent.server

import com.tanfuqi.sicent.server.filter.BeatAliveFactory
import com.tanfuqi.sicent.protocol.ProtocolCodecFactory
import com.tanfuqi.sicent.protocol.message.Login
import com.tanfuqi.sicent.protocol.message.Message
import com.tanfuqi.sicent.protocol.message.ShopInfo
import com.tanfuqi.sicent.server.advice.ShopNotFoundException
import com.tanfuqi.sicent.server.filter.ReceiveFilter
import com.tanfuqi.sicent.server.handler.MinaServerHandler
import org.apache.mina.core.filterchain.IoFilter
import org.apache.mina.core.filterchain.IoFilterAdapter
import org.apache.mina.core.service.IoAcceptor
import org.apache.mina.core.session.IdleStatus
import org.apache.mina.core.session.IoSession
import org.apache.mina.filter.codec.ProtocolCodecFilter
import org.apache.mina.filter.executor.ExecutorFilter
import org.apache.mina.filter.keepalive.KeepAliveFilter
import org.apache.mina.filter.logging.LoggingFilter
import org.apache.mina.transport.socket.nio.NioSocketAcceptor
import org.slf4j.LoggerFactory
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import reactor.core.publisher.toMono
import springfox.documentation.swagger2.annotations.EnableSwagger2WebFlux
import java.net.InetAddress
import java.net.InetSocketAddress
import java.text.SimpleDateFormat
import java.util.*

@SpringBootApplication
@EnableSwagger2WebFlux
open class Application {
    private val port = 11018
    private val logger = LoggerFactory.getLogger("com.tanfuqi.sicent.server.login")
    @Bean
    open fun acceptor(): IoAcceptor {
        val acceptor = NioSocketAcceptor()
        // 设置过滤器
        acceptor.filterChain.addLast(
                "serverCodec",
                ProtocolCodecFilter(
                        ProtocolCodecFactory))
        acceptor.filterChain.addLast("ServerFilter",
                ExecutorFilter())
        acceptor.filterChain.addLast("loggingFilter",
                LoggingFilter())
        acceptor.filterChain.addLast("pushFilter", object : IoFilterAdapter() {
            override fun messageReceived(nextFilter: IoFilter.NextFilter?, session: IoSession?, message: Any?) {
                Mono.justOrEmpty(message).cast(Message::class.java).filter {
                    it.type in 0x8080..0x808b
                }.map(Message::body).cast(MutableMap::class.java).flatMap {
                    it as MutableMap<String, Any?>
                    it["down_time"] = Mono.justOrEmpty<String>(it["down_time"]as?String).map {
                        SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(it)
                    }.defaultIfEmpty(Date()).map { "${it.time / 1000}" }.block()
                    it["flow_type"] = (message as?Message)?.type
                    it["shop_id"] = session?.getAttribute("shopId") as String
                    it["ip_addr"] = (session?.remoteAddress as? InetSocketAddress)?.hostName
                    println("推送消息:$it")
                    WebClient.create("http://api.xingyunol.com/api/v1/push").post()
                            .body(Mono.justOrEmpty(it as Map<*, *>), Map::class.java).retrieve()
                            .bodyToMono(Map::class.java).doOnSuccess {
                                println("推送成功:$it")

                            }
                }.subscribe()
                super.messageReceived(nextFilter, session, message)
            }

        })
        acceptor.filterChain.addLast("loginFilter",
                object : ReceiveFilter() {
                    override fun sessionOpened(nextFilter: IoFilter.NextFilter?, session: IoSession?) {
                        super.sessionOpened(nextFilter, session)
                        logger.info("new shop login ${(session?.remoteAddress as? InetSocketAddress)?.hostString}")
                        session?.write(Message(body = Login()))
                    }

                    override fun messageReceived(nextFilter: IoFilter.NextFilter?, session: IoSession?, message: Any?) {
                        if (message is Message && 0x80008001L.toInt() == message.type) {
                         logger.info("new shop login response ${(session?.remoteAddress as?  InetSocketAddress)?.hostString}")

                            session?.setAttribute("shopId", Mono.justOrEmpty(message.body)
                                    .cast(Map::class.java)
                                    .map { it["body"] }.cast(Map::class.java).map { it["id"] }.block())
                            Mono.justOrEmpty(session?.getAttribute("shopId")).log().subscribe()
                        }
                        super.messageReceived(nextFilter, session, message)
                    }

                    override fun sessionClosed(nextFilter: IoFilter.NextFilter?, session: IoSession?) {
                        logger.info("session closed: ${(session?.remoteAddress as? InetSocketAddress)?.hostString}")


                    }
                })
        val keepAlive = KeepAliveFilter(BeatAliveFactory(), IdleStatus.WRITER_IDLE)
        keepAlive.isForwardEvent = true
        keepAlive.requestInterval = 15
        acceptor.filterChain.addLast("keepHeart", keepAlive)
        // 设置读取数据的缓冲区大小
        acceptor.sessionConfig.readBufferSize = 1024 * 102400
        // 读写通道10秒内无操作进入空暇状态
        acceptor.sessionConfig.setIdleTime(IdleStatus.BOTH_IDLE, 10)
        // 加入逻辑处理器
        acceptor.handler = MinaServerHandler()
        // 绑定端口
        acceptor.bind(InetSocketAddress(port))
        return acceptor
    }


}

fun Mono<IoAcceptor>.getSession(shop: ShopInfo): Mono<IoSession> =
        this.flatMap { service ->
            shop.toMono().flatMap { shop ->
                Mono.justOrEmpty<IoSession>(service.managedSessions.values.find { session ->
                    shop.id == session.getAttribute("shopId")
                }).switchIfEmpty(Mono.error(ShopNotFoundException))
            }
        }

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}